// import chalk = require('chalk');
const log = {
  log(...args: any[]) {
    return console.log(...args);
  },
  success(...args: any[]) {
    return console.log(...args);
  },
  warn(...args: any[]) {
    return console.log(...args);
  },
  info(...args: any[]) {
    return console.log(...args);
  },
  error(...args: any[]) {
    return console.log(...args);
  },
};

interface LoggerOptions {
  tag?: string | (() => string);
  time?: boolean;
  enable?: boolean;
}

interface LoggerConfig {
  enable: boolean;
  time: boolean;
}

function getSimpleTimeTag() {
  const date = new Date();
  const h = date.getHours();
  const m = date.getMinutes();
  const s = date.getSeconds();
  const ms = date.getMilliseconds();

  function i(s: number, fillLength = 2) {
    let r = String(s);
    while (r.length < fillLength) {
      r = '0' + r;
    }
    return r;
  }

  return [i(h), i(m), [i(s), i(ms, 3)].join('.')].join(':');
}

function normalizeOptions(o: LoggerOptions = {}) {
  return {
    ...o,
    tag: '',
    time: true,
    enable: true,
  };
}

export default class Logger {
  private static _config = {
    enable: true,
    time: true,
  };

  public static config(options: Partial<LoggerConfig>) {
    Logger._config = Object.assign(Logger._config, options || {});
    return Logger;
  }

  private readonly tag: () => string;
  private readonly time: boolean | undefined;
  private readonly enable: boolean | undefined;

  static with(tag?: string | { time?: boolean; tag?: string; enable?: boolean }) {
    return <T extends { new (...args: any[]): {} }>(Cls: T | any) => {
      const options: Partial<LoggerOptions> = {
        tag: Cls.name,
      };
      if (typeof tag === 'string') {
        options.tag = tag;
      } else if (tag) {
        options.time = tag.time;
        options.tag = tag.tag || Cls.constructor.name;
        options.enable = tag.enable;
      }

      Cls.prototype.logger = new Logger({
        ...options,
      });
      return Cls;
    };
  }

  static getInstance(options?: LoggerOptions) {
    return new Logger(options);
  }

  private constructor(options?: LoggerOptions) {
    const { tag: t, time, enable } = normalizeOptions(options);
    const tag = typeof t === 'function' ? t : () => t;
    this.time = time;
    this.tag = tag;
    this.enable = enable;
  }

  private isEnable() {
    return typeof this.enable !== 'undefined' ? this.enable : Logger._config.enable;
  }

  private showTime() {
    return typeof this.time !== 'undefined' ? this.time : Logger._config.time;
  }

  private getArgs(args: any[]) {
    args.unshift(`${this.getTagPrefix()} `);
    return args;
  }

  private getTagPrefix() {
    const tags = this.showTime() ? [getSimpleTimeTag()] : [];
    return (this.tag() ? tags.concat(this.tag()) : tags).map((i) => `【${i}】`).join('') + ':';
  }

  private run(cb: Function) {
    if (!this.isEnable()) {
      return;
    }
    return cb();
  }

  log(...args: any[]) {
    return this.run(() => log.log(...this.getArgs(args)));
  }

  info(...args: any[]) {
    return this.run(() => log.log(...this.getArgs(args)));
  }

  error(...args: any[]) {
    return this.run(() => log.error(...this.getArgs(args)));
  }

  warn(...args: any[]) {
    return this.run(() => log.warn(...this.getArgs(args)));
  }

  success(...args: any[]) {
    return this.run(() => log.success(...this.getArgs(args)));
  }
}
